﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading;
using JetBrains.Annotations;
#if TASKSUPPORT
using System.Threading.Tasks;
#endif
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Models;
using MugenMvvmToolkit.Utils;
using MugenMvvmToolkit.ViewModels;

namespace MugenMvvmToolkit.Infrastructure
{
    /// <summary>
    ///     Represents a base class for a validator.
    /// </summary>
    //    [DebuggerDisplay("Type = {GetType()}, Errors = {GetAllErrors()}")]
    public abstract class ValidatorBase : IValidator
    {
        #region Fields

        private static readonly IDictionary<string, ICollection<string>> EmptyDictionary;
        private static readonly IList<string> EmptyList;

#if TASKSUPPORT
        /// <summary>
        /// Gets the empty collection.
        /// </summary>
        [Obsolete("Use the EmptyValidateResult value")]
        protected static readonly Task<IEnumerable> EmptyListResult;

        /// <summary>
        /// Gets the empty collection.
        /// </summary>
        protected static readonly Task<IEnumerable> EmptyValidateResult;

        /// <summary>
        /// Gets the empty dictionary.
        /// </summary>
        [Obsolete("Use the EmptyValidateAllResult value")]
        protected static readonly Task<IDictionary<string, IEnumerable>> EmptyDictionaryResult;

        /// <summary>
        /// Gets the empty dictionary.
        /// </summary>
        protected static readonly Task<IDictionary<string, IEnumerable>> EmptyValidateAllResult;
#else
        /// <summary>
        /// Gets the empty collection.
        /// </summary>
        [Obsolete("Use the EmptyValidateResult value")]
        protected static readonly IOperationResult<IEnumerable> EmptyListResult;

        /// <summary>
        /// Gets the empty collection.
        /// </summary>
        protected static readonly IOperationResult<IEnumerable> EmptyValidateResult;

        /// <summary>
        /// Gets the empty dictionary.
        /// </summary>
        [Obsolete("Use the EmptyValidateAllResult value")]
        protected static readonly IOperationResult<IDictionary<string, IEnumerable>> EmptyDictionaryResult;

        /// <summary>
        /// Gets the empty dictionary.
        /// </summary>
        protected static readonly IOperationResult<IDictionary<string, IEnumerable>> EmptyValidateAllResult;
#endif
        /// <summary>
        /// Gets the dictionary that contains all errors.
        /// </summary>
        protected readonly Dictionary<string, IEnumerable> InternalErrors;

        private readonly WeakListenersCollection _listeners;
        private readonly object _locker;
        private int _validationThreadCount;
        private bool _isDisposing;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="ValidatorBase"/> class.
        /// </summary>
        static ValidatorBase()
        {
            EmptyDictionary = new Dictionary<string, ICollection<string>>();
            EmptyList = new string[0];
            EmptyValidateResult = FromResult<IEnumerable>(null);
            EmptyValidateAllResult = FromResult<IDictionary<string, IEnumerable>>(null);

#pragma warning disable 618
            EmptyDictionaryResult = EmptyValidateAllResult;
            EmptyListResult = EmptyValidateResult;
#pragma warning restore 618
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="ValidatorBase" /> class.
        /// </summary>
        protected ValidatorBase()
        {
            _locker = new object();
            InternalErrors = new Dictionary<string, IEnumerable>();
            _listeners = new WeakListenersCollection();
#if !NONOTIFYDATAERROR
            ValidateOnPropertyChanged = true;
#endif
        }

        #endregion

        #region Implementation of IValidator

        /// <summary>
        /// Indicates that can be only once instance of this validator.
        /// </summary>
        public virtual bool IsUnique
        {
            get { return true; }
        }

        /// <summary>
        ///     Gets the initialized state of the validator.
        /// </summary>
        public bool IsInitialized
        {
            get { return Context != null; }
        }

#if !NONOTIFYDATAERROR
        /// <summary>
        /// Gets or sets the value, that indicates that the validator will be validate property on changed. Default is true.
        /// </summary>
        public bool ValidateOnPropertyChanged { get; set; }
#endif

        /// <summary>
        ///     Gets the validator context.
        /// </summary>
        public IValidatorContext Context { get; private set; }

        /// <summary>
        ///     Checks to see whether the validator can validate objects of the specified IValidatorContext.
        /// </summary>
        public bool CanValidate(IValidatorContext validatorContext)
        {
            Should.NotBeNull(validatorContext, "validatorContext");
            Should.NotBeNull(validatorContext.InstanceToValidate, "validatorContext.InstanceToValidate");
            return CanValidateContext(validatorContext) && CanValidateInternal(validatorContext);
        }

        /// <summary>
        ///     Initializes the current validator using the specified <see cref="IValidatorContext" />.
        /// </summary>
        /// <param name="context">
        ///     The specified <see cref="IValidatorContext" />.
        /// </param>
        public void Initialize(IValidatorContext context)
        {
            Should.NotBeNull(context, "context");
            using (DeadLockDetector.Lock(_locker))
            {
                if (Context != null)
                    Thrower.ValidatorWasInitialized();
                if (!CanValidate(context))
                    Thrower.ValidatorInvalidContex();
                Context = context;
            }
            OnInitialized(context);
#if !NONOTIFYDATAERROR
            var notifyPropertyChanged = InstanceToValidate as INotifyPropertyChanged;
            if (notifyPropertyChanged == null) return;
            notifyPropertyChanged.PropertyChanged += OnPropertyChangedNotifyDataError;
#endif
        }

        /// <summary>
        ///     Gets the validation errors for a specified property or for the entire entity.
        /// </summary>
        /// <returns>
        ///     The validation errors for the property or entity.
        /// </returns>
        /// <param name="propertyName">
        ///     The name of the property to retrieve validation errors for; or null or <see cref="F:System.String.Empty" />, to retrieve entity-level errors.
        /// </param>
        public IEnumerable GetErrors(string propertyName)
        {
            Should.PropertyBeNotNull(Context, "Context");

            using (DeadLockDetector.Lock(_locker))
            {
                return GetErrorsInternal(propertyName) ?? Enumerable.Empty<object>();
            }
        }

        /// <summary>
        ///     Gets the all validation errors.
        /// </summary>
        /// <returns>
        ///     The validation errors.
        /// </returns>
        public IDictionary<string, IList<object>> GetAllErrors()
        {
            Should.PropertyBeNotNull(Context, "Context");

            using (DeadLockDetector.Lock(_locker))
            {
                return GetAllErrorsInternal();
            }
        }

#if TASKSUPPORT
        /// <summary>
        ///     Updates information about errors in the specified property.
        /// </summary>
        /// <param name="propertyName">The specified property name.</param>
        public Task Validate(string propertyName)
        {
            Should.PropertyBeNotNull(Context, "Context");
            if (IgnoreProperties.Contains(propertyName))
                return MvvmUtils.FalseTaskResult;

            var values = PropertiesMapping
                .Where(list => list.Value.Contains(propertyName))
                .ToList();
            if (values.Count == 0)
                values.Add(new KeyValuePair<string, ICollection<string>>(propertyName, null));

            var tasks = new Task[values.Count];
            for (int index = 0; index < values.Count; index++)
            {
                var name = values[index].Key;
                var task = ValidateInternal(name);
                var continuationOptions = task.GetContinuationOptions();
                var isCompleted = task.IsCompleted;
                AsyncValidationMessage message = null;
                if (!isCompleted)
                {
                    message = new AsyncValidationMessage(Guid.NewGuid(), name, false);
                    Publish(message);
                    Interlocked.Increment(ref _validationThreadCount);
                }

                task = task
                    .ContinueWith(task1 =>
                                  {
                                      try
                                      {
                                          var result = task1.Result;
                                          UpdateErrorsInternal(name, result, !isCompleted);
                                          return result;
                                      }
                                      finally
                                      {
                                          if (!isCompleted)
                                          {
                                              Interlocked.Decrement(ref _validationThreadCount);
                                              if (message != null)
                                                  Publish(message.ToEndMessage());
                                          }
                                      }
                                  }, continuationOptions);
                tasks[index] = task;
            }
            return MvvmExtensionsAdvanced.WhenAll(tasks);
        }

        /// <summary>
        ///     Updates information about all errors.
        /// </summary>
        public Task ValidateAll()
        {
            Should.PropertyBeNotNull(Context, "Context");
            //Clear old errors.
            ClearErrors();

            var task = ValidateAllInternal();
            var isCompleted = task.IsCompleted;
            AsyncValidationMessage message = null;
            if (!isCompleted)
            {
                message = new AsyncValidationMessage(Guid.NewGuid(), string.Empty, false);
                Publish(message);
                Interlocked.Increment(ref _validationThreadCount);
            }
            return task.ContinueWith(task1 =>
                {
                    try
                    {
                        var result = task1.Result;
                        if (result != null)
                        {
                            foreach (var valuePair in result)
                                UpdateErrorsInternal(valuePair.Key, valuePair.Value, !isCompleted);
                        }
                    }
                    finally
                    {
                        if (!isCompleted)
                        {
                            Interlocked.Decrement(ref _validationThreadCount);
                            if (message != null)
                                Publish(message.ToEndMessage());
                        }
                    }
                }, task.GetContinuationOptions());
        }

#else
        /// <summary>
        ///     Updates information about errors in the specified property.
        /// </summary>
        /// <param name="propertyName">The specified property name.</param>
        public IOperationResult Validate(string propertyName)
        {
            Should.NotBeNull(propertyName, "propertyName");
            Should.PropertyBeNotNull(Context, "Context");
            if (IgnoreProperties.Contains(propertyName))
                return MvvmUtils.FalseOperationResult;

            var values = PropertiesMapping
                .Where(list => list.Value.Contains(propertyName))
                .ToList();
            if (values.Count == 0)
                values.Add(new KeyValuePair<string, ICollection<string>>(propertyName, null));

            var operationResults = new IOperationResult[values.Count];
            for (int index = 0; index < values.Count; index++)
            {
                var name = values[index].Key;
                var operationResult = ValidateInternal(name);
                var isCompleted = operationResult.IsCompleted;
                AsyncValidationMessage message = null;
                if (!isCompleted)
                {
                    message = new AsyncValidationMessage(Guid.NewGuid(), name, false);
                    Publish(message);
                    Interlocked.Increment(ref _validationThreadCount);
                }

                operationResult.BeginWait(result =>
                {
                    try
                    {
                        var errors = result.Result;
                        UpdateErrorsInternal(name, errors, !isCompleted);
                    }
                    finally
                    {
                        if (!isCompleted)
                        {
                            Interlocked.Decrement(ref _validationThreadCount);
                            if (message != null)
                                Publish(message.ToEndMessage());
                        }
                    }
                });

                operationResults[index] = operationResult;
            }
            return MvvmExtensionsAdvanced
                .WhenAll(operationResults);
        }

        /// <summary>
        ///     Updates information about all errors.
        /// </summary>
        public IOperationResult ValidateAll()
        {
            Should.PropertyBeNotNull(Context, "Context");

            //Clear old errors.
            ClearErrors();

            var operationResult = ValidateAllInternal();
            var isCompleted = operationResult.IsCompleted;
            AsyncValidationMessage message = null;
            if (!isCompleted)
            {
                message = new AsyncValidationMessage(Guid.NewGuid(), string.Empty, false);
                Publish(message);
                Interlocked.Increment(ref _validationThreadCount);
            }
            operationResult.BeginWait(result =>
            {
                try
                {
                    var errors = result.Result;
                    if (errors != null)
                    {
                        foreach (var valuePair in errors)
                            UpdateErrorsInternal(valuePair.Key, valuePair.Value, !isCompleted);
                    }
                }
                finally
                {
                    if (!isCompleted)
                    {
                        Interlocked.Decrement(ref _validationThreadCount);
                        if (message != null)
                            Publish(message.ToEndMessage());
                    }
                }
            });
            return operationResult;
        }
#endif
        /// <summary>
        ///     Clears errors for a property.
        /// </summary>
        /// <param name="propertyName">The name of the property</param>
        public void ClearErrors(string propertyName)
        {
            Should.NotBeNull(propertyName, "propertyName");
            ClearErrorsInternal(propertyName);
        }

        /// <summary>
        ///     Clears the all errors.
        /// </summary>
        public void ClearErrors()
        {
            ClearErrorsInternal();
        }

        /// <summary>
        ///     Determines whether the current model is valid.
        /// </summary>
        /// <returns>
        ///     If <c>true</c> current model is valid, otherwise <c>false</c>.
        /// </returns>
        public bool IsValid()
        {
            Should.PropertyBeNotNull(Context, "Context");

            using (DeadLockDetector.Lock(_locker))
            {
                return IsValidInternal();
            }
        }

        /// <summary>
        ///     Creates a new validator that is a copy of the current instance.
        /// </summary>
        /// <returns>
        ///     A new validator that is a copy of this instance.
        /// </returns>
        public IValidator Clone()
        {
            var clone = CloneInternal();
            var validatorBase = clone as ValidatorBase;
            if (validatorBase != null)
            {
#if !NONOTIFYDATAERROR
                validatorBase.ValidateOnPropertyChanged = ValidateOnPropertyChanged;
#endif
            }
            return clone;
        }

        #endregion

        #region Properties

        /// <summary>
        ///     Gets the object to validate.
        /// </summary>
        [NotNull]
        protected object InstanceToValidate
        {
            get
            {
                var instanceToValidate = Context.InstanceToValidate;
                Should.NotBeNull(instanceToValidate, "Context.InstanceToValidate");
                return instanceToValidate;
            }
        }

        /// <summary>
        ///     Gets the mapping of error properties.
        /// </summary>
        [NotNull]
        protected internal IDictionary<string, ICollection<string>> PropertiesMapping
        {
            get
            {
                if (Context == null)
                    return EmptyDictionary;
                return Context.PropertiesMapping;
            }
        }

        /// <summary>
        ///     Gets the list of properties that will not be validated.
        /// </summary>
        [NotNull]
        protected internal ICollection<string> IgnoreProperties
        {
            get
            {
                if (Context == null)
                    return EmptyList;
                return Context.IgnoreProperties;
            }
        }

        /// <summary>
        /// Gets a value that indicates that the validator at the moment there are threads in which there is a validation.
        /// </summary>
        protected bool IsValidating
        {
            get { return _validationThreadCount != 0; }
        }

        #endregion

        #region Virtual-abstract methods

        /// <summary>
        ///     Publishes a message.
        /// </summary>
        /// <param name="message">The message instance.</param>
        protected virtual void Publish(object message)
        {
            Should.NotBeNull(message, "message");
            _listeners.Publish(this, message);
        }

        /// <summary>
        ///     Initializes the current validator using the specified <see cref="IValidatorContext" />.
        /// </summary>
        /// <param name="context">
        ///     The specified <see cref="IValidatorContext" />.
        /// </param>
        protected virtual void OnInitialized(IValidatorContext context)
        {
        }

        /// <summary>
        ///     Gets the all validation errors.
        /// </summary>
        /// <returns>
        ///     The validation errors.
        /// </returns>
        protected virtual IDictionary<string, IList<object>> GetAllErrorsInternal()
        {
            if (InternalErrors.Count == 0)
                return new Dictionary<string, IList<object>>();
            return InternalErrors.CloneDictionary();
        }

        /// <summary>
        ///     Gets the validation errors for a specified property or for the entire entity.
        /// </summary>
        /// <returns>
        ///     The validation errors for the property or entity.
        /// </returns>
        /// <param name="propertyName">
        ///     The name of the property to retrieve validation errors for; or null or <see cref="F:System.String.Empty" />, to retrieve entity-level errors.
        /// </param>
        protected virtual IEnumerable GetErrorsInternal(string propertyName)
        {
            if (InternalErrors.Count == 0)
                return Enumerable.Empty<object>();

            if (string.IsNullOrEmpty(propertyName))
            {
                return InternalErrors
                    .Values
                    .SelectMany(enumerable => enumerable.OfType<object>())
                    .ToArray();
            }

            IEnumerable errors;
            if (!InternalErrors.TryGetValue(propertyName, out errors) || errors == null)
                return Enumerable.Empty<object>();
            return errors
                .OfType<object>()
                .ToArray();
        }

        /// <summary>
        ///     Determines whether the current model is valid.
        /// </summary>
        /// <returns>
        ///     If <c>true</c> current model is valid, otherwise <c>false</c>.
        /// </returns>
        protected virtual bool IsValidInternal()
        {
            return InternalErrors.Count == 0 && !IsValidating;
        }

        /// <summary>
        ///     Clears errors for a property.
        /// </summary>
        /// <param name="propertyName">The name of the property</param>
        protected virtual void ClearErrorsInternal(string propertyName)
        {
            UpdateErrorsInternal(propertyName, null, false);
        }

        /// <summary>
        ///     Clears the all errors.
        /// </summary>
        protected virtual void ClearErrorsInternal()
        {
            string[] keys;
            using (DeadLockDetector.Lock(_locker))
                keys = InternalErrors.Keys.ToArray();
            foreach (var source in keys)
                UpdateErrorsInternal(source, null, false);
        }

        /// <summary>
        ///     Occurs after current view model disposed, use for clear resource and event listeners.
        /// </summary>
        protected virtual void OnDispose(bool disposing)
        {
        }

        /// <summary>
        ///     Checks to see whether the validator can validate objects of the specified IValidatorContext.
        /// </summary>
        internal virtual bool CanValidateContext(IValidatorContext validatorContext)
        {
            return true;
        }

        /// <summary>
        ///     Checks to see whether the validator can validate objects of the specified IValidatorContext.
        /// </summary>
        protected virtual bool CanValidateInternal(IValidatorContext validatorContext)
        {
            return true;
        }

#if TASKSUPPORT
        /// <summary>
        ///     Updates information about errors in the specified property.
        /// </summary>
        /// <param name="propertyName">The specified property name.</param>        
        /// <returns>
        ///     The result of validation.
        /// </returns>
        protected abstract Task<IEnumerable> ValidateInternal(string propertyName);

        /// <summary>
        ///     Updates information about all errors.
        /// </summary>
        /// <returns>
        ///     The result of validation.
        /// </returns>
        protected abstract Task<IDictionary<string, IEnumerable>> ValidateAllInternal();
#else
        /// <summary>
        ///     Updates information about errors in the specified property.
        /// </summary>
        /// <param name="propertyName">The specified property name.</param>
        protected abstract IOperationResult<IEnumerable> ValidateInternal(string propertyName);

        /// <summary>
        ///     Updates information about all errors.
        /// </summary>        
        protected abstract IOperationResult<IDictionary<string, IEnumerable>> ValidateAllInternal();
#endif

        /// <summary>
        ///     Creates a new validator that is a copy of the current instance.
        /// </summary>
        /// <returns>
        ///     A new validator that is a copy of this instance.
        /// </returns>
        protected virtual IValidator CloneInternal()
        {
            return (IValidator)Activator.CreateInstance(GetType());
        }

        #endregion

        #region Methods

        /// <summary>
        ///     Sets validator errors for a property.
        /// </summary>
        /// <param name="propertyName">The name of the property</param>
        /// <param name="errors">The collection of errors</param>
        protected internal void SetErrors(string propertyName, params object[] errors)
        {
            UpdateErrorsInternal(propertyName, errors, false);
        }

        /// <summary>
        ///     Raises this object's ErrorsChangedChanged event.
        /// </summary>
        /// <param name="propertyName">The property that has new errors.</param>
        /// <param name="isAsyncValidate">Indicates that property was async validation.</param>
        protected virtual void OnErrorsChanged(string propertyName, bool isAsyncValidate)
        {
            Publish(new DataErrorsChangedMessage(propertyName, isAsyncValidate));
#if !NONOTIFYDATAERROR
            EventHandler<DataErrorsChangedEventArgs> handler = ErrorsChanged;
            if (handler == null) return;
            handler(this, new DataErrorsChangedEventArgs(propertyName));
#endif
        }

#if !TASKSUPPORT
        /// <summary>
        ///     Creates a <see cref="IOperationResult{T}" /> that's completed successfully with the specified result.
        /// </summary>
        /// <returns>
        ///     The successfully completed operation.
        /// </returns>
        /// <param name="result">The result to store into the completed operation.</param>
        /// <typeparam name="TResult">The type of the result returned by the operation.</typeparam>
        protected static IOperationResult<TResult> FromResult<TResult>(TResult result)
        {
            return OperationResult.FromResult(result);
        }
#else
        /// <summary>
        /// Creates a <see cref="T:System.Threading.Tasks.Task`1"/> that's completed successfully with the specified result.
        /// </summary>        
        /// <returns>
        /// The successfully completed task.
        /// </returns>
        /// <param name="result">The result to store into the completed task.</param><typeparam name="TResult">The type of the result returned by the task.</typeparam>
        protected static Task<TResult> FromResult<TResult>(TResult result)
        {
            return MvvmExtensions.FromResult(result);
        }
#endif
        /// <summary>
        ///     Sets errors for a property.
        /// </summary>
        /// <param name="propertyName">The name of the property.</param>
        /// <param name="propertyErrors">The collection of errors.</param>
        /// <param name="isAsyncValidate">Indicates that property was async validation.</param>
        private void UpdateErrorsInternal(string propertyName, IEnumerable propertyErrors, bool isAsyncValidate)
        {
            if (IgnoreProperties.Contains(propertyName)) return;
            ICollection<string> mappingProperties;
            var errorToUpdate = InternalErrors;
            PropertiesMapping.TryGetValue(propertyName, out mappingProperties);
            using (DeadLockDetector.Lock(_locker))
            {
                if (mappingProperties == null)
                {
                    if (propertyErrors == null || !propertyErrors.OfType<object>().Any())
                        errorToUpdate.Remove(propertyName);
                    else
                        errorToUpdate[propertyName] = propertyErrors;
                }
                else
                {
                    foreach (var property in mappingProperties)
                    {
                        if (propertyErrors == null || !propertyErrors.OfType<object>().Any())
                            errorToUpdate.Remove(property);
                        else
                            errorToUpdate[property] = propertyErrors;
                    }
                }
            }
            if (mappingProperties == null)
            {
                OnErrorsChanged(propertyName, isAsyncValidate);
                return;
            }
            foreach (var mappingProperty in mappingProperties)
                OnErrorsChanged(mappingProperty, isAsyncValidate);
        }

        /// <summary>
        /// Checks whether the properties are equal.
        /// </summary>
        /// <param name="propertyName">The specified property name.</param>
        /// <param name="getProperty">The expression to get property.</param>
        /// <returns>If true property equals, otherwise false.</returns>
        protected static bool PropertyNameEqual<T>(string propertyName, Expression<Func<T, object>> getProperty)
        {
            return MvvmExtensions.PropertyNameEqual(propertyName, getProperty);
        }

        #endregion

        #region Implementation of IDataErrorInfo

#if !NODATAERROR
        /// <summary>
        /// Gets the error message for the property with the given name.
        /// </summary>
        /// <returns>
        /// The error message for the property. The default is an empty string ("").
        /// </returns>
        /// <param name="columnName">The name of the property whose error message to get. 
        ///                 </param>
        public virtual string this[string columnName]
        {
            get
            {
                Validate(columnName);
                var errors = GetErrors(columnName);
                var error = errors
                    .OfType<object>()
                    .FirstOrDefault();
                if (error == null)
                    return null;
                return error.ToString();
            }
        }

        /// <summary>
        /// Gets an error message indicating what is wrong with this object.
        /// </summary>
        /// <returns>
        /// An error message indicating what is wrong with this object. The default is an empty string ("").
        /// </returns>
        public virtual string Error
        {
            get
            {
                var stB = new StringBuilder();
                IEnumerable enumerable = GetErrors(string.Empty);
                foreach (var value in enumerable)
                {
                    if (value == null) continue;
                    stB.AppendLine(value.ToString());
                }
                return stB.ToString();
            }
        }
#endif
        #endregion

        #region Implementation of INotifyDataErrorInfo

#if !NONOTIFYDATAERROR
        /// <summary>
        /// Gets a value that indicates whether the entity has validation errors. 
        /// </summary>
        /// <returns>
        /// true if the entity currently has validation errors; otherwise, false.
        /// </returns>
        bool INotifyDataErrorInfo.HasErrors
        {
            get { return !IsValid(); }
        }

        /// <summary>
        /// Occurs when the validation errors have changed for a property or for the entire entity. 
        /// </summary>
        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        /// <summary>
        ///     Notifies on errors when any property changed.
        /// </summary>
        protected void OnPropertyChangedNotifyDataError(object sender, PropertyChangedEventArgs args)
        {
            if (args == null || args.PropertyName == null || !ValidateOnPropertyChanged || !IsInitialized) return;
            Validate(args.PropertyName);
        }
#endif

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            using (DeadLockDetector.Lock(_locker))
            {
                if (_isDisposing) return;
                _isDisposing = true;
            }
            GC.SuppressFinalize(this);
            OnDispose(true);
#if !NONOTIFYDATAERROR
            var notifyPropertyChanged = InstanceToValidate as INotifyPropertyChanged;
            if (notifyPropertyChanged == null) return;
            notifyPropertyChanged.PropertyChanged -= OnPropertyChangedNotifyDataError;
#endif
        }

        #endregion

        #region Destructor

        /// <summary>
        /// Destructor of validator.
        /// </summary>
        ~ValidatorBase()
        {
            OnDispose(false);
        }

        #endregion

        #region Implementation of IObservable

        /// <summary>
        ///     Subscribes an instance to events.
        /// </summary>
        /// <param name="instance">The instance to subscribe for event publication.</param>
        public virtual bool Subscribe(object instance)
        {
            if (instance == this)
                return false;
            return _listeners.Add(instance);
        }

        /// <summary>
        ///     Unsubscribes the instance from all events.
        /// </summary>
        /// <param name="instance">The instance to unsubscribe.</param>
        public virtual bool Unsubscribe(object instance)
        {
            return _listeners.Remove(instance);
        }

        #endregion
    }

    /// <summary>
    ///     Represents a base class for a validator.
    /// </summary>
    public abstract class ValidatorBase<T> : ValidatorBase
    {
        #region Properties

        /// <summary>
        ///     Gets the object to validate.
        /// </summary>
        [NotNull]
        protected new T InstanceToValidate
        {
            get { return (T)base.InstanceToValidate; }
        }

        #endregion

        #region Methods

        /// <summary>
        ///     Sets validator errors for a property.
        /// </summary>
        /// <param name="propertyExpresssion">The expression for the property</param>
        /// <param name="errors">The collection of errors</param>
        protected internal void SetErrors(Expression<Func<T, object>> propertyExpresssion, params object[] errors)
        {
            SetErrors(MvvmUtilsInternal.ParsePropertyExpression(propertyExpresssion), errors);
        }

        /// <summary>
        /// Checks whether the properties are equal.
        /// </summary>
        /// <param name="propertyName">The specified property name.</param>
        /// <param name="getProperty">The expression to get property.</param>
        /// <returns>If true property equals, otherwise false.</returns>
        protected static bool PropertyNameEqual(string propertyName, Expression<Func<T, object>> getProperty)
        {
            return PropertyNameEqual<T>(propertyName, getProperty);
        }

        #endregion

        #region Overrides of ValidatorBase

        /// <summary>
        ///     Checks to see whether the validator can validate objects of the specified IValidatorContext.
        /// </summary>
        internal override bool CanValidateContext(IValidatorContext validatorContext)
        {
            return validatorContext.InstanceToValidate is T;
        }

        #endregion
    }
}